from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER,CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.mac import haddr_to_bin
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ether_types
from ryu.topology import event
from ryu.topology.api import get_host
from ryu.controller import ofp_event

import networkx as nx

VXLANs_CONFIG={
    100:{"VNI":100,"ROUTING_ALGORITHM":"Hop"},
    200:{"VNI":200,"ROUTING_ALGORITHM":"Hop"}
}
VTEPs_CONFIG={
    1:{"DPID":1,"VXLAN_PORT_FOR_FORWARD":{100:5,200:6},"LOCAL_PORT_FOR_TAG":{3:100,4:200}},
    2:{"DPID":2,"VXLAN_PORT_FOR_FORWARD":{},"LOCAL_PORT_FOR_TAG":{}},
    3:{"DPID":3,"VXLAN_PORT_FOR_FORWARD":{100:5,200:6},"LOCAL_PORT_FOR_TAG":{3:200,4:100}}
}

FORWARDING_RULEs={
    1:[{"in_port":3,"out_port":5},{"in_port":4,"out_port":6},{"in_port":5,"out_port":3},{"in_port":6,"out_port":4}],
    3:[{"in_port":3,"out_port":6},{"in_port":4,"out_port":5},{"in_port":5,"out_port":4},{"in_port":6,"out_port":3}]
}

class VXLAN:
    def __init__(self,vxlan_config):
        self.Vni=vxlan_config["VNI"]
        self.Routing_Algorithm=vxlan_config["ROUTING_ALGORITHM"]
        
class VTEP:
    def __init__(self,vtep_config):
        self.Dpid=vtep_config["DPID"] 
        
        #初始化数据包应当从哪个端口转发
        self.Vxlan_Port_For_Forward={}
        for vni,vxlan_port in vtep_config["VXLAN_PORT_FOR_FORWARD"].items():
            self.Vxlan_Port_For_Forward[vni]=vxlan_port
            
        #初始化本地数据包应当被打上哪个vni标签
        self.Local_Port_For_Tag={}
        for local_port,vni in vtep_config["LOCAL_PORT_FOR_TAG"].items():
            self.Local_Port_For_Tag[local_port]=vni
                        
class MULTI_TENANT_NETWORK(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
    
    def __init__(self,*args,**kwargs):
        super(MULTI_TENANT_NETWORK, self).__init__(*args, **kwargs)
        #实例化各vxlan
        self.Vxlans={}
        for vni,vxlan_config in VXLANs_CONFIG.items():
            self.Vxlans[vni]=VXLAN(vxlan_config)
        
        #实例化各vtep
        self.Vteps={}
        for dpid,vtep_config in VTEPs_CONFIG.items():
            self.Vteps[dpid]=VTEP(vtep_config)
        
        #实例化转发表
        # self.Forwarding_Table=FORWARDING_TABLE(FORWARDING_RULEs)
        
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
    def switchFeaturesHandler(self,ev):
        datapath=ev.msg.datapath
        ofp_proto=datapath.ofproto
        ofp_parser=datapath.ofproto_parser
        match=ofp_parser.OFPMatch()
        inst=[ofp_parser.OFPInstructionGotoTable(1)]
        
        dpid=datapath.id
        #self.Forwarding_Table[dpid]["datapath"]=datapath  #暂时似乎不需要存
        #print("Switch with DPID = ", dpid, "is up and connected to the Controller..")
        
        #默认表项目：将数据包发送到表1处理
        mod=ofp_parser.OFPFlowMod(datapath=datapath,table_id=0,priority=0,match=match,instructions=inst)
        datapath.send_msg(mod)
        print("Table 0 flow entry added to s{0} to send packets to Table 1".format(dpid))
        
        #表1默认条目：将数据包发送给控制器
        actions = [ofp_parser.OFPActionOutput(ofp_proto.OFPP_CONTROLLER, ofp_proto.OFPCML_NO_BUFFER)]
        inst = [ofp_parser.OFPInstructionActions(ofp_proto.OFPIT_APPLY_ACTIONS, actions)]
        mod = ofp_parser.OFPFlowMod(
            datapath=datapath, table_id=1, priority=0, match=match, instructions=inst)
        datapath.send_msg(mod)
        print("Table 1 flow entry added to s{0} to send packets to the Controller".format(dpid))
        
        #添加表项，从本地端口发送的数据包打上vni标签
        for local_port,vni in self.Vteps[dpid].Local_Port_For_Tag.items():
            match=ofp_parser.OFPMatch(in_port=local_port) #匹配从本地端口发送的数据包
            actions=[ofp_parser.NXActionSetTunnel(tun_id=int(vni))]  #本地数据包打上vni标签
            inst=[ofp_parser.OFPInstructionActions(ofp_proto.OFPIT_APPLY_ACTIONS,actions),   
                  ofp_parser.OFPInstructionGotoTable(1)]     #action和inst有什么区别？
            mod=ofp_parser.OFPFlowMod(datapath=datapath,priority=100,match=match,instructions=inst)
            datapath.send_msg(mod)        
            print("Table 0 flow entry added to s{0} for local_port={1} with VNI ID={2}".format(dpid,local_port,vni))
            
        #添加表项，从VxLAN端口接收的数据包转发
        # for vni,vxlan_port in self.Vteps[dpid].Vxlan_Port_For_Forward.items():
        #     match=ofp_parser.OFPMatch(tunnel_id=int(vni))
        #     actions=[ofp_parser.OFPActionOutput(port=vxlan_port)]
        #     inst=[ofp_parser.OFPInstructionActions(ofp_proto.OFPIT_APPLY_ACTIONS,actions)]
        #     mod=ofp_parser.OFPFlowMod(datapath=datapath,table_id=1,priority=100,match=match,instructions=inst)
        #     datapath.send_msg(mod)
        #     print("交换机{}: VNI={}添加表1流表项，输出端口{}".format(dpid,vni,vxlan_port))
        for rule in FORWARDING_RULEs[dpid]:
            in_port=rule["in_port"]
            out_port=rule["out_port"]
            match=ofp_parser.OFPMatch(in_port=in_port)
            actions=[ofp_parser.OFPActionOutput(port=out_port)]
            inst=[ofp_parser.OFPInstructionActions(ofp_proto.OFPIT_APPLY_ACTIONS,actions)]
            mod=ofp_parser.OFPFlowMod(datapath=datapath,table_id=1,priority=100,match=match,instructions=inst)
            datapath.send_msg(mod)
            print("交换机{}: 添加表1流表项，输入端口{}，输出端口{}".format(dpid,in_port,out_port))
        
        
    
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packetInHander(self,ev):
        msg=ev.msg
        datapath=msg.datapath
        dpid=datapath.id
        
        ofp_proto=datapath.ofproto
        ofp_parser=datapath.ofproto_parser
        pkt=packet.Packet(msg.data)
        eth = pkt.get_protocols(ethernet.ethernet)[0]
        
        if eth.ethertype == ether_types.ETH_TYPE_LLDP:
            return
        
        in_port = msg.match['in_port']
        try:
            vni = msg.match['tunnel_id']  
        except KeyError as e:
            #print("数据包未被封装")
            return
        #print("Received a packet from {0} on port={1}, with VNI ID={2} from eth_src={3} to eth_dst={4}".format(dpid,in_port,vni,eth.src,eth.dst))
        # rule={(eth.src,vni):in_port}
        # self.Forwarding_Table.Rules[dpid]=rule
        # vxlan_ports=self.Vteps[dpid].Vxlan_Port_For_Forward[vni][:]
        # local_ports=list(self.Vteps[dpid].Local_Port_For_Tag.keys())
        
        # if eth.dst=='ff:ff:ff:ff:ff:ff':
        #     print("**********************广播报文**********************")
        #     if in_port in vxlan_ports:
        #         print("广播报文从VxLAN端口进入，向所有本地端口广播报文")
        #         for port in local_ports:
        #             actions=[ofp_parser.OFPActionOutput(port)]
        #             out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=ofp_proto.OFP_NO_BUFFER, in_port=(in_port),
        #                                         actions=actions, data=pkt)
        #             datapath.send_msg(out)
        #             print("交换机{}: 从VxLAN端口{}广播报文到本地端口{}".format(dpid,in_port,port))
        #     else:
        #         print("广播报文从本地端口进入，向所有除了入端口的本地端口广播报文")         
        #         local_ports.remove(in_port)
        #         for port in local_ports:
        #             actions=[ofp_parser.OFPActionOutput(port)]
        #             out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=ofp_proto.OFP_NO_BUFFER, in_port=(in_port),
        #                                         actions=actions, data=pkt)
        #             datapath.send_msg(out)
        #             print("交换机{}: 从本地端口{}广播报文到本地端口{}".format(dpid,in_port,port))
            
        #     #print("广播报文从VxLAN端口进入，数据包被打上标签后转发") #应当不需要吧，单个域内被隔离
        #     # for port in vxlan_ports:  # Multicast operation
        #     #     actions = [ofp_parser.NXActionSetTunnel(tun_id=int(vni)), ofp_parser.OFPActionOutput(port=port)]
        #     #     out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=ofp_proto.OFP_NO_BUFFER, in_port=in_port,actions=actions, data=pkt)
        #     #     datapath.send_msg(out)
                
        #     #     print("{0} : {1} Packet output in_port={2} setTunnelId={3} out_port={4}".format(dpid, 0, in_port, vni,port))
        
        # else:
        #     print("**********************单播报文**********************")
        #     try:
        #         out_port=self.Forwarding_Table.Rules[dpid][(eth.dst,vni)]
        #     except KeyError as e:
        #         print("交换机{}: 未找到匹配的流表项，向控制器发送数据包".format(dpid))
        #         # ?这些数据包要不要向控制器发送？
        #         return
            
        #     match=ofp_parser.OFPMatch(tunnel_id=int(vni),eth_dst=eth.dst)
        #     actions=[ofp_parser.OFPActionOutput(port=out_port)]
        #     inst=[ofp_parser.OFPInstructionActions(
        #         ofp_proto.OFPIT_APPLY_ACTIONS,actions)]
        #     mod=ofp_parser.OFPFlowMod(datapath=datapath,table_id=1,priority=100,match=match,instructions=inst)
        #     datapath.send_msg(mod)
        #     print("交换机{}: 为eth_dst={}和VNI={}添加表1流表项，输出端口{}".format(dpid,eth.dst,vni,out_port))
        #     local_ports=list(self.Vteps[dpid].Local_Port_For_Tag.keys())
        #     if in_port  in local_ports:
        #         actions = [ofp_parser.NXActionSetTunnel(tun_id=int(vni)),ofp_parser.OFPActionOutput(port=out_port)]
        #     else:
        #         actions = [ofp_parser.OFPActionOutput(port=out_port)]
                
        #     out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=ofp_proto.OFP_NO_BUFFER, in_port=in_port,actions=actions, data=pkt)
        #     datapath.send_msg(out)
        #     print("交换机{}：为数据包打上标签后转发到端口{}".format(dpid,out_port))